package com.zzvcom.doc.html;


import cn.hutool.core.util.StrUtil;
import com.zzvcom.doc.dto.RequestHtmlDTO;
import com.zzvcom.doc.util.PinYinUtil;
import com.zzvcom.doc.util.OSUtils;
import org.apache.commons.io.FileUtils;
import org.docx4j.Docx4J;
import org.docx4j.Docx4jProperties;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.ConversionFeatures;
import org.docx4j.convert.out.HTMLSettings;
import org.docx4j.convert.out.html.SdtToListSdtTagHandler;
import org.docx4j.convert.out.html.SdtWriter;
import org.docx4j.fonts.*;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.jsoup.Jsoup;
import org.jsoup.nodes.*;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.transform.*;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.List;

/**
 * This sample uses XSLT (and Xalan) to
 * produce HTML output.  (There is also
 * HtmlExporterNonXSLT for environments where
 * that is not desirable eg Android).
 * <p>
 * If the source docx contained a WMF, that
 * will get converted to inline SVG.  In order
 * to see the SVG in your browser, you'll need
 * to rename the file to .xml or serve
 * it with MIME type application/xhtml+xml
 */
public class CoreConvertOutHtml {

    private static final Logger log = LoggerFactory.getLogger(CoreConvertOutHtml.class);
    private static URIResolver RESOURCES_URI_RESOLVER = new OutHtmlURIResolver();

    /**
     * xslt模板url识别器
     */
    static class OutHtmlURIResolver implements URIResolver {
        @Override
        public Source resolve(String href, String base) throws TransformerException {
            log.debug("base={}", base);
            try {
                return new StreamSource(
                        org.docx4j.utils.ResourceUtils.getResource(
                                href));
            } catch (IOException e) {
                throw new TransformerException(e);
            }
        }
    }

    /**
     * 使用自身定义的模板
     *
     * @return
     * @throws Docx4JException
     */
    static synchronized Templates loadCustTemplates() throws Docx4JException {
        Source xsltSource = null;
        Templates ret = null;
        URIResolver originalURIResolver = null;
        String defaultTemplatesResource = null;
        try {
            originalURIResolver = XmlUtils.getTransformerFactory().getURIResolver();
            // TODO FIXME - partially thread safe,
            // loading of Templates in the delegates is synchronized on the
            // XmlUtils.getTransformerFactory() but other parts of the application
            // are not.
            XmlUtils.getTransformerFactory().setURIResolver(RESOURCES_URI_RESOLVER);
            if (Docx4jProperties.getProperty("docx4j.Convert.Out.HTML.OutputMethodXML", true)) {
                log.info("Outputting well-formed XHTML..");
                defaultTemplatesResource = "docx2xhtml.xslt";
            } else {
                log.info("Outputting HTML tag soup..");
                defaultTemplatesResource = "docx2html.xslt";
            }
            xsltSource = new StreamSource(org.docx4j.utils.ResourceUtils.getResource(
                    defaultTemplatesResource));
            ret = XmlUtils.getTransformerTemplate(xsltSource);
        } catch (IOException e) {
            throw new Docx4JException("Exception loading template \"" + defaultTemplatesResource + "\", " + e.getMessage(), e);
        } catch (TransformerConfigurationException e) {
            throw new Docx4JException("Exception loading template \"" + defaultTemplatesResource + "\", " + e.getMessage(), e);
        } finally {
            XmlUtils.getTransformerFactory().setURIResolver(originalURIResolver);
        }
        return ret;
    }

    //自定义模板
    static Templates custTemplates;
    private static boolean nestLists;

    static {
        nestLists = true;
        try {
            custTemplates = loadCustTemplates();
        } catch (Docx4JException e) {
            throw new RuntimeException(e);
        }
    }

    public static String process(InputStream inputStream, RequestHtmlDTO requestHtmlDTO) {
        // Document loading (required)
        WordprocessingMLPackage wordMLPackage = null;
        // HTML exporter setup (required)
        // .. the HTMLSettings object
        HTMLSettings htmlSettings = Docx4J.createHTMLSettings();
        try {
            wordMLPackage = Docx4J.load(inputStream);
            //设置为空就会采用base64作为img的src
            htmlSettings.setImageDirPath("");
            //去掉注zhushi
            htmlSettings.setUserBodyTail("");
            htmlSettings.setUserBodyTop("");
            htmlSettings.setOpcPackage(wordMLPackage);
            htmlSettings.setCustomXsltTemplates(custTemplates);

           /* String userCSS = null;
            if (nestLists) {
                // use browser defaults for ol, ul, li
                userCSS = "html, body, div, span, h1, h2, h3, h4, h5, h6, p, a, img,  table, caption, tbody, tfoot, thead, tr, th, td " +
                        "{ margin: 0; padding: 0; border: 0;}" +
                        "body {line-height: 1;}"
                //  +
                // " table{border-collapse: collapse;}"+
                //  " tr,td { border: 1px solid #000;padding: 8px;}"
                ;
            } else {
                userCSS = "html, body, div, span, h1, h2, h3, h4, h5, h6, p, a, img,  ol, ul, li, table, caption, tbody, tfoot, thead, tr, th, td " +
                        "{ margin: 0; padding: 0; border: 0;}" +
                        "body {line-height: 1;} ";

            }
            htmlSettings.setUserCSS(userCSS);*/
          /*  htmlSettings.setStyleElementHandler(new ConversionHTMLStyleElementHandler(){

                @Override
                public org.w3c.dom.Element createStyleElement(OpcPackage opcPackage, org.w3c.dom.Document document, String styleDefinition) {

                    return null;
                }
            });*/

            // list numbering:  depending on whether you want list numbering hardcoded, or done using <li>.
            if (nestLists) {
                 SdtWriter.registerTagHandler("HTML_ELEMENT", new SdtToListSdtTagHandler());
            } else {
                htmlSettings.getFeatures().remove(ConversionFeatures.PP_HTML_COLLECT_LISTS);
            }
            // Set up font mapper (optional)
            // We don't add CSS for a font which isn't physically present.
            // TODO: consider web fonts?
            Mapper fontMapper;
            if (OSUtils.isWindows()) {
                fontMapper = new IdentityPlusMapper(); // better for Windows
            } else {
                fontMapper = new BestMatchingMapper(); // better for Linux
            }
            fontMapper.put("宋体", PhysicalFonts.get("simsun"));
            fontMapper.put("黑体", PhysicalFonts.get("simhei"));
            fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft YaHei"));

            try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
                wordMLPackage.setFontMapper(fontMapper);

                // If you want XHTML output
                Docx4jProperties.setProperty("docx4j.Convert.Out.HTML.OutputMethodXML", true);
                Docx4J.toHTML(htmlSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
                String htmlContent = os.toString("UTF-8");
                log.info("HTML: " + htmlContent);
                htmlContent = convertByJsoup(htmlContent, requestHtmlDTO);
                log.info("HTML2: " + htmlContent);

                return htmlContent;
            }
        } catch (Exception e) {
            log.error("Exception: ", e);
        } finally {
            // Clean up, so any ObfuscatedFontPart temp files can be deleted
            try {
                if (wordMLPackage != null && wordMLPackage.getMainDocumentPart() != null &&
                        wordMLPackage.getMainDocumentPart().getFontTablePart() != null) {
                    wordMLPackage.getMainDocumentPart().getFontTablePart().deleteEmbeddedFontTempFiles();
                }
            } catch (Exception cleanupException) {
                log.warn("Failed to clean up font table temp files", cleanupException);
            }
        }
        return null;
    }

    private static String convertByJsoup(String htmlContent, RequestHtmlDTO requestHtmlDTO) {
        try {
            // 使用 jsoup 解析 HTML
            Document doc = Jsoup.parse(htmlContent);
            //很关键，转公式的时候，还需要
            doc.outputSettings().prettyPrint(false);
            convertMathToTex(doc);
            //添加样式前缀
            HtmlStyleProcessUtil.addStylePrefix(doc, requestHtmlDTO.getStylePrefix());

            if (requestHtmlDTO.isMathjax()) {
                // 紧凑的 MathJax 配置
                doc.body().append("<script>MathJax={tex:{inlineMath:[['$','$'],['\\\\(','\\\\)']]},mml:{displayMath:[['\\\\[','\\\\]']]},chtml:{scale:1.0,color:'#333',fontCache:'global'},loader:{load:['input/mml','output/chtml','ui/menu']}};</script>");
                doc.body().append("<script id=\"MathJax-script\"  src=\"" + requestHtmlDTO.getContextPath() + "/mathjax/tex-chtml.js\"></script>");
            }
            //拼音字体转换
            if (requestHtmlDTO.isForcepinyin()) {
                convertPinyin(doc);
            }
            String result = doc.html();
            //仅仅要样式和body里面的内容，其他不需要
            if (requestHtmlDTO.isOnlyCore()) {

                Elements styles = doc.select("style");
                StringBuilder coreStrBuilder = new StringBuilder();
                for (Element style : styles) {
                    String styleContent = style.outerHtml();
                    coreStrBuilder.append(styleContent);
                }
                coreStrBuilder.append("\n").append(doc.body().html());
                result = coreStrBuilder.toString();
            }
            //调试方式下保存文件
            if (requestHtmlDTO.isSaveFileDebug()) {
                FileUtils.writeStringToFile(new File(requestHtmlDTO.getSaveFileBugPath()
                                + System.nanoTime() + ".html"),
                        result, "UTF-8");
            }
            // 返回修改后的 HTML
            return result;
        } catch (Exception e) {
            log.error("Error processing MathML to LaTeX conversion with jsoup", e);
            return null;
        }
    }

    /**
     * 对于a \u0251 强制使用拼音,尝试
     *
     * @param doc
     */
    private static void convertPinyin(Document doc) {
        Elements span = doc.select("span");
        if (span == null && span.isEmpty()) {
            return;
        }
        for (Element element : span) {
            boolean isPINYIN = false;
            if (element.hasText()) {
                String text = element.text();
                for (int i = 0; i < text.length(); i = text.offsetByCodePoints(i, 1)) {
                    char c = text.charAt(i);
                    log.info("char: " + c);
                    if (PinYinUtil.isPinyinChar(c)) {
                        isPINYIN = true;
                        break;
                    }
                }
            }
            //暂时
            if (isPINYIN) {
                //style="font-size: 14.0pt;;font-family: 'SimHei';"
                Attribute style = element.attribute("style");
                if (style != null) {
                    StringBuilder styleValue = new StringBuilder();
                    String value = style.getValue();
                    if (value != null) {
                        List<String> split = StrUtil.split(value, ';');
                        for (String s : split) {
                            List<String> tmpValue = StrUtil.split(s, ':');
                            if (tmpValue.size() == 2) {
                                String key = tmpValue.get(0);
                                String val = tmpValue.get(1);
                                if ("font-family".equals(key)) {
                                    log.info("word 定义的 font-family: " + val);
                                    styleValue.append(key).append(":").append(val).append(",").append("'pinyin'").append(";");
                                } else {
                                    styleValue.append(key).append(":").append(val).append(";");
                                }
                            }
                        }
                    }
                    element.attr("style", styleValue.toString());
                }
            }
        }
    }

    /**
     * 转换 MathML 为 TeX
     * 参考 https://github.com/transpect/mml2tex/blob/master/xsl/mml2tex.xsl
     *
     * @param doc
     */
    private static void convertMathToTex(Document doc) {
        // 查找所有 math 元素
        Elements mathElements = doc.select("math");
        if (mathElements != null && !mathElements.isEmpty()) {
            // 遍历并转换每个 math 元素
            for (Element mathElement : mathElements) {
                try {
                    // 获取 math 元素的 outer HTML（包含标签本身）
                    String mathML = mathElement.outerHtml();
                    // 调用转换器
                    String latex = Mml2TexConverter.convertMathMLFromString(mathML);
                    // 创建新的文本节点替换原元素
                    Node latexNode = new TextNode("$$ " + latex + " $$");
                    mathElement.replaceWith(latexNode);
                } catch (Exception e) {
                    log.warn("Failed to convert MathML to LaTeX", e);
                    // 转换失败时保留原元素
                }
            }
        }
    }


}
